package com.sgb.goods.utils.module;

import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 商品组件代理
 * <p>
 *     调用参数使用基本类型，实现函数的参数使用对应的包装类；如：
 *     call1(int,String,long) -> abc(Integer,String,Long)
 * </p>
 * @author cyj
 */
public class GoodsModule {

    /** 日志输出开关 */
    private boolean isDebug;
    /** 注册组件集合 */
    private Map<String, String> clsMap = new LinkedHashMap<>();
    /** Class绑定缓存集合 */
    private Map<String, String> bindMap = new LinkedHashMap<>();
    /** 是否参数构造标记 */
    private boolean isParamsConstructor;
    /**
     * 最近一次可回收的 Class component 组件实例。
     * 1.节约性能开销的缓存重用，它只会存在一个 Class 实例；
     * 2.也可以通过 call1/call2 调用后使用此{@link this#clear()}来回收它；
     */
    private WeakReference<Class<?>> clsInstance;
    /** paramsCtr 参数构造-参数集合 */
    private Object[] paramsCtrArray;

    private GoodsModule() {}

    private static class Single {
        private static final GoodsModule INSTANCE = new GoodsModule();
    }

    public synchronized static GoodsModule getInstance() {
        return Single.INSTANCE;
    }

    /**
     * 注册商品组件代理实现类
     * @param cls
     */
    public void registerComponent(Class<?> cls) {
        if(null == cls) {
            return;
        }
        try {
            GoodsComponent annotation = cls.getAnnotation(GoodsComponent.class);
            if(null != annotation) {
                clsMap.put(cls.getSimpleName(), cls.getName());
                log("[GoodsModule] register component [" + annotation.value() + "] " + cls.getSimpleName() + "is success!");
                analysisField(cls);
                log("[GoodsModule] component analysisField is success!");
            }
        } catch (Throwable e) {
            log("[GoodsModule] register Component is error! trace: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * call1，提供的无返回对象的调用接口
     * @param methodName
     * @param objects
     */
    public void call1(String methodName, Object... objects) {
        call2(methodName, objects);
    }

    /**
     * call2，提供的有返回对象的调用接口
     * 内封装处理所有异常情况，根据返回值判断是否调用成功
     * @param methodName
     * @param objects
     * @return    null：规定返回null为异常；如果无返回值，可以通过设定返回一个文本"success"来告知调用是否成功；
     */
    final public synchronized Object call2(String methodName, Object... objects) {
        if(null == methodName || methodName.length() <= 0) {
            throw new Error("Don't allow methodName is null!");
        }
        try {
            if(null != objects) {
                return reflect(methodName, convertParamsToArray(objects));
            }
            return reflect(methodName);
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 参数构建器设置参数
     * 用于带参构造器初始化
     * @param objects    参数集
     * @return    GoodsModule
     */
    public GoodsModule buildParamsConstructor(Object... objects) {
        this.isParamsConstructor = true;
        this.paramsCtrArray = convertParamsToArray(objects);
        return this;
    }

    /**
     * 转换对象参数为一个大小为2的组合类型+数值的对象数组
     */
    private Object[] convertParamsToArray(Object... objects) {
        if(null == objects) {
            return null;
        }
        Object[] paramsObjects = new Object[2];
        Class<?>[] clsArray = new Class[objects.length];
        for(int i = 0; i < objects.length; i ++) {
            clsArray[i] = objects[i].getClass();
        }
        paramsObjects[0] = clsArray;
        paramsObjects[1] = objects;
        return paramsObjects;
    }

    /**
     * reflect。调用方法。
     */
    private Object reflect(String methodName, Object... paramsObjects) throws Throwable {
        // 获取绑定缓存
        String bindObject = bindMap.get(methodName);
        if(null == bindObject) {
            return null;
        }

        // 解析绑定缓存内容
        String[] bindArray = bindObject.split("\\|");
        String bindClsName = null == bindArray[0] ? "" : bindArray[0];
        String bindClsMethodName = bindArray[1];

        // clsInstance 是否被GC回收，bindCls 实例化
        Class<?> bindCls = null;
        if(null != clsInstance && null != clsInstance.get() && bindClsName.equals(clsInstance.get().getSimpleName())) {
            bindCls = clsInstance.get();
        } else {
            bindCls = Class.forName(Objects.requireNonNull(clsMap.get(bindClsName)));
            clear();
            clsInstance = new WeakReference<>(bindCls);
        }

        // 映射 Method
        Method method = null == paramsObjects ? bindCls.getDeclaredMethod(bindClsMethodName) :
                bindCls.getDeclaredMethod(bindClsMethodName, (Class<?>[]) paramsObjects[0]);
        method.setAccessible(true);

        // Constructor 构造 目标Class实例
        Constructor<?> constructor;
        Object object;
        if(isParamsConstructor) {
            constructor = null == paramsCtrArray ? bindCls.getDeclaredConstructor() : bindCls.getDeclaredConstructor((Class<?>[]) paramsCtrArray[0]);
            object = null == paramsCtrArray ? constructor.newInstance() : constructor.newInstance((Object[]) paramsCtrArray[1]);
        } else {
            constructor = bindCls.getDeclaredConstructor();
            object = constructor.newInstance();
        }
        constructor.setAccessible(true);

        return null == paramsObjects ? method.invoke(object) : method.invoke(object, (Object[]) paramsObjects[1]);
    }

    /**
     * 解析字段域【运行时】
     * @param cls
     * @throws Throwable
     */
    private void analysisField(Class<?> cls) throws Throwable {
        Method[] declaredMethods = cls.getMethods();
        for(Method method : declaredMethods) {
            method.setAccessible(true);
            Module annotation = method.getAnnotation(Module.class);
            if(annotation != null) {
                String value = annotation.value();
                bindMap.put(value, cls.getSimpleName() + "|" + method.getName());
            }
        }
    }

    public void printRegisterMethod() {
        if(null == bindMap) {
            return;
        }
        log("[GoodsModule] All register method start.");
        for(Map.Entry<String, String> entry : bindMap.entrySet()) {
            log("This register method name is : " + entry.getKey());
        }
        log("[GoodsModule] All register method end.");
    }

    public void log(String str) {
        if(isDebug) {
            System.out.println(str);
        }
    }

    public void setDebug(boolean isDebug) {
        this.isDebug = isDebug;
    }

    /**
     * 清理回收实例
     */
    public void clear() {
        if(null != clsInstance) {
            clsInstance.clear();
        }
    }
}
